package co.fitstart.admin.service.impl;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.demo2do.core.BusinessException;
import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dynamic;
import com.demo2do.core.support.Page;
import com.demo2do.core.support.Result;
import com.demo2do.core.utils.CollectionUtils;
import com.demo2do.core.utils.DateFormatUtils;
import com.demo2do.core.utils.DateUtils;

import co.fitstart.admin.dao.CoachDao;
import co.fitstart.admin.dao.CoachRestCalendarDao;
import co.fitstart.admin.dao.CourseDaoSupport;
import co.fitstart.admin.dao.CourseRoundDao;
import co.fitstart.admin.dao.CourseRoundResultDao;
import co.fitstart.admin.dao.CourseScheduleDao;
import co.fitstart.admin.dao.DietaryProgramTemplateDao;
import co.fitstart.admin.dao.FitnessTagDao;
import co.fitstart.admin.dao.GroupDao;
import co.fitstart.admin.dao.GroupTemplateDao;
import co.fitstart.admin.dao.PrincipalDao;
import co.fitstart.admin.dao.ScheduleDaoSupport;
import co.fitstart.admin.entity.course.ExperienceStat;
import co.fitstart.admin.entity.search.CourseRoundFeedbackSearch;
import co.fitstart.admin.entity.search.CourseRoundSearch;
import co.fitstart.admin.entity.security.Login;
import co.fitstart.admin.handler.PaymentHandler;
import co.fitstart.admin.handler.SmsSender;
import co.fitstart.admin.handler.TencentHandler;
import co.fitstart.admin.service.CourseRoundService;
import co.fitstart.entity.Constant;
import co.fitstart.entity.coach.CoachRestCalendar;
import co.fitstart.entity.coach.CoachRestType;
import co.fitstart.entity.coach.PrincipalReportRelation;
import co.fitstart.entity.course.Course;
import co.fitstart.entity.course.CourseType;
import co.fitstart.entity.course.round.CourseRound;
import co.fitstart.entity.course.round.CourseRoundAttribute;
import co.fitstart.entity.course.round.CourseRoundFeedback;
import co.fitstart.entity.course.round.CourseRoundResult;
import co.fitstart.entity.course.round.OnlineRoundGroupStatus;
import co.fitstart.entity.course.round.RoundStatus;
import co.fitstart.entity.course.schedule.CourseSchedule;
import co.fitstart.entity.course.schedule.ScheduleStatus;
import co.fitstart.entity.courseware.CoursewareTag;
import co.fitstart.entity.courseware.FitnessTag;
import co.fitstart.entity.courseware.dietary.Dietary;
import co.fitstart.entity.courseware.dietary.DietaryProgram;
import co.fitstart.entity.courseware.dietary.DietaryProgramTemplate;
import co.fitstart.entity.courseware.dietary.DietaryProgramType;
import co.fitstart.entity.courseware.exercise.Exercise;
import co.fitstart.entity.courseware.fitnessgroup.BatchCourseware;
import co.fitstart.entity.courseware.fitnessgroup.BatchCoursewareTemplate;
import co.fitstart.entity.courseware.fitnessgroup.BatchCoursewareType;
import co.fitstart.entity.courseware.fitnessgroup.Courseware;
import co.fitstart.entity.courseware.fitnessgroup.CoursewareEditStatus;
import co.fitstart.entity.courseware.fitnessgroup.CoursewareTemplate;
import co.fitstart.entity.courseware.fitnessgroup.DailyCourseware;
import co.fitstart.entity.courseware.fitnessgroup.DailyCoursewareTemplate;
import co.fitstart.entity.courseware.fitnessgroup.FitnessGroup;
import co.fitstart.entity.order.Order;
import co.fitstart.entity.order.OrderStatus;
import co.fitstart.entity.order.PaymentMethod;
import co.fitstart.entity.principal.Principal;
import co.fitstart.entity.principal.PrincipalMessage;
import co.fitstart.entity.site.RoomScheduleRecord;
import co.fitstart.entity.site.RoomType;
import co.fitstart.entity.site.Site;
import co.fitstart.entity.user.Student;

@Service("courseRoundService")
public class CourseRoundServiceImpl implements CourseRoundService {

    @Autowired
    private GenericDaoSupport genericDaoSupport;

    @Dynamic
    private CourseRoundDao courseRoundDao;

    @Dynamic
    private CourseScheduleDao courseScheduleDao;

    @Dynamic
    private CoachRestCalendarDao coachRestCalendarDao;

    @Dynamic
    private PrincipalDao principalDao;

    @Dynamic
    private GroupTemplateDao groupTemplateDao;

    @Dynamic
    private GroupDao groupDao;

    @Dynamic
    private DietaryProgramTemplateDao dietaryProgramTemplateDao;

    @Dynamic
    private FitnessTagDao fitnessTagDao;

    @Dynamic
    private CoachDao coachDao;

    @Autowired
    private CourseDaoSupport courseDaoSupport;

    @Autowired
    private SmsSender smsSender;

    @Autowired
    private PaymentHandler paymentHandler;

    @Autowired
    private TencentHandler tencentHandler;

    @Dynamic
    private CourseRoundResultDao courseRoundResultDao;

    @Autowired
    private ScheduleDaoSupport scheduleDaoSupport;

    private static final Logger logger = LoggerFactory.getLogger(CourseRoundServiceImpl.class);
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#countLastCourseNull()
     */
    @Override
    public int countLastCourseNull() {
        Map<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("disabled", false);
        return genericDaoSupport.count("FROM CourseRound WHERE disabled = :disabled and lastCourse IS NULL", parameters);
    }
    
    /**
     * 
     */
    @SuppressWarnings("unchecked")
    @Override
    public void updateLastCourse() {
        
        List<Student> students = genericDaoSupport.searchForList("SELECT DISTINCT(round.student) FROM CourseRound round WHERE round.disabled = false and round.lastCourse IS NULL");
        for(Student student : students) {
            Map<String, Object> parameters = new HashMap<String, Object>();
            parameters.put("student", student);
            List<CourseRound> courseRounds = genericDaoSupport.searchForList("FROM CourseRound WHERE disabled = false and student = :student ORDER BY id DESC", parameters);
            int begin = 0;
            for(int i = 0; i < courseRounds.size(); i++) {
                CourseRound round = courseRounds.get(i);
                if(i == begin) {
                    if(round.isGive() || round.getCourse().isExperience()) {
                        round.setLastCourse(false);
                        begin++;
                    } else {
                        round.setLastCourse(true);
                    }
                } else {
                    round.setLastCourse(false);
                }
                
                genericDaoSupport.update(round);
            }
        }
        
    }
    
    /**
     * 
     */
    @SuppressWarnings("unchecked")
    @Override
    public void updateLastCourse(int limit) {
        
        List<Student> students = genericDaoSupport.searchForList("SELECT DISTINCT(round.student) FROM CourseRound round WHERE round.disabled = false and round.lastCourse IS NULL", 0, limit);
        for(Student student : students) {
            Map<String, Object> parameters = new HashMap<String, Object>();
            parameters.put("student", student);
            List<CourseRound> courseRounds = genericDaoSupport.searchForList("FROM CourseRound WHERE disabled = false and student = :student ORDER BY id DESC", parameters);
            int begin = 0;
            for(int i = 0; i < courseRounds.size(); i++) {
                CourseRound round = courseRounds.get(i);
                if(i == begin) {
                    if(round.isGive() || round.getCourse().isExperience()) {
                        round.setLastCourse(false);
                        begin++;
                    } else {
                        round.setLastCourse(true);
                    }
                } else {
                    round.setLastCourse(false);
                }
                
                genericDaoSupport.update(round);
            }
        }
        
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.ecommerce.CourseRoundService#list(co.fitstart.entity.site.Site)
     */
    @SuppressWarnings("unchecked")
    public Map<String, List<CourseRound>> listCourseRound(Site site) {
        
        String sentence = "FROM CourseRound WHERE disabled = false AND site = :site AND courseType in ( :courseTypes )";
        
        List<CourseType> courseTypes = new ArrayList<CourseType>();
        courseTypes.add(CourseType.INDIVIDUAL);
        courseTypes.add(CourseType.SPECIAL);
        courseTypes.add(CourseType.POSTURE);
        courseTypes.add(CourseType.GROUP);
        courseTypes.add(CourseType.YEAR);
        
        Map<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("site", site);
        parameters.put("courseTypes", courseTypes);
        
        List<CourseRound> result = genericDaoSupport.searchForList(sentence, parameters);
        
        return CollectionUtils.constructMapValuesFromList(result, "student.id");
   
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listOfflineCourseRound(co.fitstart.entity.site.Site)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRound> listOfflineCourseRound(Site site) {
        String sentence = "FROM CourseRound WHERE disabled = false AND site = :site AND courseType in ( :courseTypes )";
        
        List<CourseType> courseTypes = new ArrayList<CourseType>();
        courseTypes.add(CourseType.INDIVIDUAL);
        courseTypes.add(CourseType.SPECIAL);
        courseTypes.add(CourseType.POSTURE);
        courseTypes.add(CourseType.GROUP);
        courseTypes.add(CourseType.YEAR);
        
        Map<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("site", site);
        parameters.put("courseTypes", courseTypes);
        
        return genericDaoSupport.searchForList(sentence, parameters);
        
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#list(co.fitstart.entity.site.Site, co.fitstart.entity.course.round.RoundStatus, co.fitstart.entity.course.CourseType, boolean, boolean, com.demo2do.core.support.Page)
     */
    public List<CourseRound> list(Site site, RoundStatus roundStatus, CourseType courseType, boolean experience, boolean give, Page page) {
        
        List<CourseRound> result = new ArrayList<CourseRound>();
        
        if(site == null) {
            result = courseRoundDao.listIndividualRound(courseType, roundStatus, experience, give, page.getBeginIndex(), page.getMaxResultRecords());
        } else {
            result = courseRoundDao.listIndividualRound(site, courseType, roundStatus, experience, give, page.getBeginIndex(), page.getMaxResultRecords());
        }
        
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#list(java.util.List, co.fitstart.entity.course.round.RoundStatus, co.fitstart.entity.course.CourseType, boolean, boolean, com.demo2do.core.support.Page)
     */
    public List<CourseRound> list(List<Site> sites, RoundStatus roundStatus, CourseType courseType, boolean experience,
           boolean give, Page page) {
        List<CourseRound> result = courseRoundDao.listIndividualRound(sites, courseType, roundStatus, experience, give, page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OfflineRoundService#list(co.fitstart.entity.site.Site, co.fitstart.entity.course.round.RoundStatus, co.fitstart.entity.course.CourseType, com.demo2do.core.support.Page)
     */
    public List<CourseRound> list(Site site, RoundStatus roundStatus, CourseType courseType, Page page) {
        
        List<CourseRound> result = new ArrayList<CourseRound>();
        
        if(site == null) {
            result = courseRoundDao.listIndividualRound(courseType, roundStatus, page.getBeginIndex(), page.getMaxResultRecords());
        } else {
            result = courseRoundDao.listIndividualRound(site, courseType, roundStatus, page.getBeginIndex(), page.getMaxResultRecords());
        }
        
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#list(java.util.List, co.fitstart.entity.course.round.RoundStatus, co.fitstart.entity.course.CourseType, com.demo2do.core.support.Page)
     */
    public List<CourseRound> list(List<Site> sites, RoundStatus roundStatus, CourseType courseType, Page page) {
        List<CourseRound> result = courseRoundDao.listIndividualRound(sites, courseType, roundStatus, page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.ecommerce.CourseRoundService#loadCourseRound(java.lang.Long)
     */
    public CourseRound loadCourseRound(Long courseRoundId) {
        return genericDaoSupport.load(CourseRound.class, courseRoundId);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listCourseRound(co.fitstart.entity.user.Student, co.fitstart.entity.course.CourseType[])
     */
    public List<CourseRound> listCourseRound(Student student, CourseType[] courseTypes) {
        return courseRoundDao.listCourseRound(student, courseTypes);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.CourseRoundService#changeScheduleTime(java.util.List, java.util.Date, java.lang.String, co.fitstart.entity.course.CourseType)
     */
    public void changeScheduleTime(List<CourseSchedule> courseSchedules, Date day, String hour, CourseType type) {
        // Step1 update site schedule record time
        
        String occurDay = DateFormatUtils.format(day, "yyyy-MM-dd");
        
       // compare date & today, is date small than day throw exception 
        Date currentDateTime = DateUtils.parseDate(occurDay + " " + hour + ":00", "yyyy-MM-dd HH:mm:SS");
        if(currentDateTime.before(new Date())) {
            throw new BusinessException("所选时间小于当前时间，请重新选择");
        }
        
        // Step2 update course schedule time
        for(CourseSchedule courseSchedule : courseSchedules) {
            genericDaoSupport.update(courseSchedule.editTime(day, hour));
        }
        
        // Step3 send message to coach
        Principal coach = courseSchedules.get(0).getCoach();
        if(coach != null) {
           PrincipalMessage principalMessage = new PrincipalMessage(coach);
            
           if (type.equals(CourseType.INDIVIDUAL) || type.equals(CourseType.SPECIAL) || type.equals(CourseType.POSTURE)) {
               genericDaoSupport.save(principalMessage.initialize("单节课程改课时间", "您有新的单节课程时间", "您有新的单节课程时间已被修改，学员为【" + courseSchedules.get(0).getStudent().getName() + "】，上课时间为【"
                       + courseSchedules.get(0).getDayhour() 
                       +"】,请在我的学员点击查看。"));
               
           } else if(type.equals(CourseType.GROUP)) {
               
               genericDaoSupport.save(principalMessage.initialize("团课单节课程时间修改", "您的团课有单节课程时间修改", "您的团课有单节课程时间修改，请在我的学员点击查看具体情况。"));
           }
        }
    }
    
    /*
     * /(non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#toggleFreeze(co.fitstart.entity.course.round.CourseRound, java.lang.Integer)
     */
    public void toggleFreeze(CourseRound courseRound, Integer holdMonth) {
        // 1. 如果courseRound是冻结的，解冻
        if (courseRound.getCourseStatus().isHold()) {
            if(courseRound.getFitnessGroup() == null) {
                genericDaoSupport.update(courseRound.editEffectiveEnd().unfreeze());
            } else {
                Integer originalWeek = null;
                if(MapUtils.isNotEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) != null) {
                    originalWeek = Integer.parseInt(courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT).getValue());
                }
                
                genericDaoSupport.update(courseRound.editEffectiveEnd().unfreeze());
                
                if(originalWeek == null) {
                    this.initBatchCourseware(courseRound);
                } else {
                    this.updateBatchCourseware(courseRound, originalWeek);
                }
            }
        } else { // 2. 不是，冻结
            if(courseRound.getFitnessGroup() == null) {
                if(courseRound.getCourseType().isYear()) {
                    genericDaoSupport.update(courseRound.freezeDay(holdMonth));
                } else {
                    genericDaoSupport.update(courseRound.freeze(holdMonth));
                }
            } else {
                
                Integer originalWeek = null;
                if(MapUtils.isNotEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) != null) {
                    originalWeek = Integer.parseInt(courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT).getValue());
                }
                
                if(courseRound.getCourseType().isYear()) {
                    genericDaoSupport.update(courseRound.freezeDay(holdMonth));
                } else {
                    genericDaoSupport.update(courseRound.freeze(holdMonth));
                }
                
                if(originalWeek == null) {
                    this.initBatchCourseware(courseRound);
                } else {
                    this.updateBatchCourseware(courseRound, originalWeek);
                }
            }
            
            //send freeze notice message
            if((courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isSpecial() || courseRound.getCourseType().isPosture()) && !courseRound.getCourse().isExperience()) {
                smsSender.sendIndividualRoundFreezeNotice(courseRound);
            } else if(courseRound.getCourseType().isYear()) {
                smsSender.sendYearRoundFreezeNotice(courseRound);
            }
        }
    }
    
    
    @Override
    public void toggleFreezeByDay(CourseRound courseRound, Integer days) {
        // TODO Auto-generated method stub
        // 1. 如果courseRound是冻结的，解冻
        if (courseRound.getCourseStatus().isHold()) {
            if(courseRound.getFitnessGroup() == null) {
                genericDaoSupport.update(courseRound.editEffectiveEnd().unfreeze());
            } else {
                Integer originalWeek = null;
                if(MapUtils.isNotEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) != null) {
                    originalWeek = Integer.parseInt(courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT).getValue());
                }
                
                genericDaoSupport.update(courseRound.editEffectiveEnd().unfreeze());
                
                if(originalWeek == null) {
                    this.initBatchCourseware(courseRound);
                } else {
                    this.updateBatchCourseware(courseRound, originalWeek);
                }
            }
        } else { // 2. 不是，冻结
            if(courseRound.getFitnessGroup() == null) {
                if(courseRound.getCourseType().isYear()) {
                    genericDaoSupport.update(courseRound.freezeDay(days));
                } else {
                    genericDaoSupport.update(courseRound.freezeDay(days));
                }
            } else {
                Integer originalWeek = null;
                if(MapUtils.isNotEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) != null) {
                    originalWeek = Integer.parseInt(courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT).getValue());
                }
                
                genericDaoSupport.update(courseRound.freezeDay(days));
                
                if(originalWeek == null) {
                    this.initBatchCourseware(courseRound);
                } else {
                    this.updateBatchCourseware(courseRound, originalWeek);
                }
            }
            
            //send freeze notice message
            if((courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isSpecial() || courseRound.getCourseType().isPosture()) && !courseRound.getCourse().isExperience()) {
                smsSender.sendIndividualRoundFreezeNotice(courseRound);
            } else if(courseRound.getCourseType().isYear()) {
                smsSender.sendYearRoundFreezeNotice(courseRound);
            }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OfflineRoundService#editExpiryTime(co.fitstart.entity.course.round.CourseRound, java.util.Date)
     */
    public void editExpiryTime(CourseRound courseRound, Date effectiveEnd) {
        genericDaoSupport.update(courseRound.editEffectiveEnd(effectiveEnd));
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#validateEditSchedule(co.fitstart.entity.course.schedule.CourseSchedule, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String, co.fitstart.entity.site.Site)
     */
    public void validateEditSchedule(CourseSchedule courseSchedule, Principal coach, Date day, String hour, Site site) {
        Date dayHour = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + hour, "yyyy-MM-dd HH:mm");
        
        if(dayHour.before(new Date())) {
            throw new BusinessException("所选时间小于当前时间");
        }
        
        this.validate(courseSchedule.getStudent(), courseSchedule.getCourseRound());
        
        this.validateEffectiveEnd(courseSchedule.getCourseRound(), day, hour);
        
        if((courseSchedule.getCourseRound().getCourseType().isIndividual() || courseSchedule.getCourseRound().getCourseType().isSpecial() || courseSchedule.getCourseRound().getCourseType().isPosture()) && courseSchedule.getCourse().isExperience()) {
            this.validateExpDayhour(day, hour, site);
        } else {
            this.validateDayhour(day, hour, site);
        }
        
        this.validateStudentTime(courseSchedule, courseSchedule.getStudent(), day, hour);
        
        if((courseSchedule.getCourseRound().getCourseType().isIndividual() || courseSchedule.getCourseRound().getCourseType().isSpecial() || courseSchedule.getCourseRound().getCourseType().isPosture()) && courseSchedule.getCourse().isExperience()) {
            this.validateCoachTime(courseSchedule, coach, day, hour);
        } else {
            this.validateCoach(courseSchedule, coach, day, hour);
        }
        
        this.validateCoachSite(coach, site, day);
    }
    
    /**
     * 
     * @param student
     * @param day
     * @param hour
     */
    private void validateStudentTime(CourseSchedule courseSchedule, Student student, Date day, String hour) {
        
        Map<String, String> hours = this.getHours(DateFormatUtils.format(day, "yyyy-MM-dd") + " " + hour);
        
        List<String> scheduleHour = new ArrayList<String>();
        scheduleHour.add(hours.get("beforeHour"));
        scheduleHour.add(hours.get("thisHour"));
        scheduleHour.add(hours.get("afterHour"));
        
        List<CourseSchedule> schedules = courseScheduleDao.listSchedules(student, day, scheduleHour);
        for (CourseSchedule schedule : schedules) {
            ScheduleStatus scheduleStatus = schedule.getScheduleStatus();
            if(!scheduleStatus.isFinish() && schedule != courseSchedule) {
                throw new BusinessException("所选时间您已有其他课程!");
            }
        }
    }
    
    /**
     * 
     * @param student
     * @param courseRound
     */
    private void validate(Student student, CourseRound courseRound) {
        
        CourseType courseType = courseRound.getCourseType();
        
        // check course type
        if(!courseType.supportAppointment()) {
            throw new BusinessException("该课程不支持预约");
        }

        // check courseRound matches student or not
        if(student == null || !courseRound.matches(student)) {
            throw new BusinessException("学员课程数据有误");
        }
        
    }
    
    /**
     * 
     * @param day
     * @param hour
     * @param site
     */
    private void validateDayhour(Date day, String hour, Site site) {
        Date selectDayhour = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + hour, "yyyy-MM-dd HH:mm");
        if(selectDayhour.before(new Date())) {
            throw new BusinessException("所选时间小于当前时间");
        }
        
        String endHour = Integer.toString(Integer.parseInt(hour.split(":")[0]) + 1) + ":" + hour.split(":")[1];
        Date selectEndDayhour = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + endHour, "yyyy-MM-dd HH:mm");
        
        Date appointStartTime = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + site.getAppointStartHour(), "yyyy-MM-dd HH:mm");
        Date appointEndTime = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + site.getAppointEndHour(), "yyyy-MM-dd HH:mm");
        if(selectDayhour.before(appointStartTime) || selectEndDayhour.after(appointEndTime)) {
            throw new BusinessException("所选时间不可预约");
        }
    }
    
    /**
     * 
     * @param day
     * @param hour
     * @param site
     */
    private void validateExpDayhour(Date day, String hour, Site site) {
        Date selectDayhour = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + hour, "yyyy-MM-dd HH:mm");
        if(selectDayhour.before(new Date())) {
            throw new BusinessException("所选时间小于当前时间");
        }
        
        String endHour = null;
        String[] hours = hour.split(":");
        if("00".equals(hours[1])) {
            int h = Integer.parseInt(hours[0]) + 1;
            endHour = Integer.toString(h) + ":" + "30";
        } else {
            int h = Integer.parseInt(hours[0]) + 2;
            endHour = Integer.toString(h) + ":" + "00";
        }
        Date selectEndDayhour = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + endHour, "yyyy-MM-dd HH:mm");
        
        Date appointStartTime = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + site.getAppointStartHour(), "yyyy-MM-dd HH:mm");
        Date appointEndTime = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + site.getAppointEndHour(), "yyyy-MM-dd HH:mm");
        if(selectDayhour.before(appointStartTime) || selectEndDayhour.after(appointEndTime)) {
            throw new BusinessException("所选时间不可预约");
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#validateYearCourse(co.fitstart.entity.user.Student, co.fitstart.entity.course.round.CourseRound, java.util.Date, java.lang.String)
     */
    public void validateYearCourse(CourseSchedule currentSchedule, Date day, String hour) {
        Student student = currentSchedule.getStudent();
        CourseRound courseRound = currentSchedule.getCourseRound();
        
        this.validateEffectiveEnd(courseRound, day, hour);
        
        // 用户一天只能预约一次
        CourseSchedule courseSchedule = courseScheduleDao.loadCourseSchedule(currentSchedule.getId(), student, day, courseRound);
        if(courseSchedule != null) {
            throw new BusinessException("本课程今日已有预约");
        }
        
        // 校验用户在本年卡是否有失信
        if(courseRound.isDishonesty()) {
         // put monday and sunday as parameter
            Date startDate = DateUtils.getFirstWeekDay(new Date());
            Date endDate = DateUtils.addDays(startDate, 6);
            
            List<CourseSchedule> thisWeekSchedules = courseScheduleDao.listCourseSchedule(student, courseRound, startDate, endDate);
            if(org.apache.commons.collections.CollectionUtils.isNotEmpty(thisWeekSchedules)) {
                throw new BusinessException("失信用户，一周只能预约一节课程");
            }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#validateCoach(co.fitstart.entity.principal.Principal, java.lang.String)
     */
    public Result validateCoach(Principal coach, String dayhour) {
        Result result = new Result();
        
        String[] dayhours = StringUtils.split(dayhour, "|");
        Date day = DateUtils.toDate(dayhours[0]);
        
        Map<String, String> hours = this.getHours(StringUtils.replace(dayhour, "|", " "));
        
        // validate rest time 
        List<String> restHours = coachRestCalendarDao.listRestHours(coach, day);
        if(restHours.contains(hours.get("thisHour")) || restHours.contains(hours.get("afterHour"))) {
            result.fail().message("当前时间为该教练休息！");
            return result;
        }
        
        
        // validate course scheudle
        List<String> courseHours = courseScheduleDao.listCourseHours(coach, day);
        if(courseHours.contains(hours.get("thisHour")) || courseHours.contains(hours.get("beforeHour")) || courseHours.contains(hours.get("afterHour"))) {
            result.fail().message("当前时间为该教练已有课程！");
            return result;
        }
        
        // validate success
        result.success().message("校验成功");
        
        return result;
    }
    
    /**
     * 
     * @param coach
     * @param day
     * @param hour
     */
    private void validateCoach(CourseSchedule currentSchedule, Principal coach, Date day, String hour) {
        Map<String, String> hours = this.getHours(DateFormatUtils.format(day, "yyyy-MM-dd") + " " + hour);
        
        List<String> scheduleHour = new ArrayList<String>();
        scheduleHour.add(hours.get("beforeHour"));
        scheduleHour.add(hours.get("thisHour"));
        scheduleHour.add(hours.get("afterHour"));
        
        List<CourseSchedule> schedules = courseScheduleDao.listSchedules(coach, day, scheduleHour);
        if(!schedules.isEmpty()) {
            if(schedules.size() != 1 || schedules.get(0) != currentSchedule) {
                throw new BusinessException("当前时间为该教练已有课程！");
            }
        }
        
        List<String> restHour = new ArrayList<String>();
        restHour.add(hours.get("thisHour"));
        restHour.add(hours.get("afterHour"));
        
        List<CoachRestCalendar> calendars = coachRestCalendarDao.list(coach, day, restHour);
        if(!calendars.isEmpty()) {
            throw new BusinessException("当前时间为该教练休息！");
        }
    }
    
    /**
     * validate coach time for experience
     * @param coach
     * @param date
     * @param hour
     */
    private void validateCoachTime(CourseSchedule courseSchedule, Principal coach, Date date, String hour) {
        Date currentDate = DateUtils.parseDate(DateFormatUtils.format(date, "yyyy-MM-dd ") + hour, "yyyy-MM-dd HH:mm");
        String beforeHalfhour = DateFormatUtils
                .format(org.apache.commons.lang3.time.DateUtils.addMinutes(currentDate, -30), "HH:mm");
        String afterHalfhour = DateFormatUtils
                .format(org.apache.commons.lang3.time.DateUtils.addMinutes(currentDate, 30), "HH:mm");
        String afterHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(currentDate, 1),
                "HH:mm");

        List<CourseSchedule> schedules = courseScheduleDao.listCourseSchedule(coach, currentDate,
                new String[] { beforeHalfhour, hour, afterHalfhour, afterHour });
        if (!schedules.isEmpty()) {
            if(schedules.size() != 1 || schedules.get(0) != courseSchedule) {
                throw new BusinessException("该时间此教练已有课程");
            }
        }

        List<CoachRestCalendar> calendars = coachRestCalendarDao.list(coach, currentDate,
                new String[] { hour, afterHalfhour, afterHour });
        if (!calendars.isEmpty()) {
            if(DateUtils.isSameDay(date, courseSchedule.getOccurDate())) {
                Date oldDate = DateUtils.parseDate(DateFormatUtils.format(courseSchedule.getOccurDate(), "yyyy-MM-dd ") + courseSchedule.getOccurHour(), "yyyy-MM-dd HH:mm");
                String oldAfterHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(oldDate, 1),
                        "HH:mm");
                if(calendars.size() != 1 || !calendars.get(0).getRestHour().equals(oldAfterHour) || !calendars.get(0).isExperience()) {
                    throw new BusinessException("该时间此教练休息");
                }
            } else {
                throw new BusinessException("该时间此教练休息");
            }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#validateCoach(co.fitstart.entity.principal.Principal, java.util.List, java.lang.String, java.lang.String)
     */
    public boolean validateCoach(Principal coach, List<String> dayhours, String thisDayhour, String selectDayhour) {
        String[] days = StringUtils.split(dayhours.get(0), "|");
        Date day = DateUtils.toDate(days[0]);
        
        List<String> hours = new ArrayList<String>();
        for (String dayhour : dayhours) {
            if(StringUtils.equalsIgnoreCase(dayhour, selectDayhour)) {
                Map<String, String> mapHours = this.getHours(StringUtils.replace(dayhour, "|", " "));
                for(String key : mapHours.keySet()) {
                    if(!StringUtils.equalsIgnoreCase(mapHours.get(key), StringUtils.split(thisDayhour, "|")[1])) {
                        hours.add(mapHours.get(key));
                    }
                }
            }
        }
        
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(hours)) {
            List<CourseSchedule> courseSchedules = courseScheduleDao.listSchedules(coach, day, hours);
            if(org.apache.commons.collections.CollectionUtils.isEmpty(courseSchedules)) {
                return true;
            }
        }
        
        
        return false;
    }
    
    /**
     * 
     * @return
     */
    private Map<String, String> getHours(String dayhour) {
        
        Date thisHour = DateUtils.parseDate(dayhour, "yyyy-MM-dd HH:mm");
        Date beforeHour = org.apache.commons.lang3.time.DateUtils.addMinutes(thisHour, -30);
        Date afterHour = org.apache.commons.lang3.time.DateUtils.addMinutes(thisHour, 30);
        
        Map<String, String> hours = new HashMap<String, String>();
        hours.put("thisHour", DateFormatUtils.format(thisHour, "HH:mm"));
        hours.put("beforeHour", DateFormatUtils.format(beforeHour, "HH:mm"));
        hours.put("afterHour", DateFormatUtils.format(afterHour, "HH:mm"));
        
        return hours;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#getDayhours(java.lang.String)
     */
    public List<String> getDayhours(String dayhour) {
        List<String> dayhours = new ArrayList<String>();
        
        Date thisHour = DateUtils.parseDate(dayhour, "yyyy-MM-dd HH:mm");
        Date beforeHour = org.apache.commons.lang3.time.DateUtils.addMinutes(thisHour, -30);
        Date afterHour = org.apache.commons.lang3.time.DateUtils.addMinutes(thisHour, 30);
        
        dayhours.add(DateFormatUtils.format(beforeHour, "yyyy-MM-dd|HH:mm"));
        dayhours.add(DateFormatUtils.format(afterHour, "yyyy-MM-dd|HH:mm"));
        return dayhours;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#stopCamp(co.fitstart.entity.course.round.CourseRound)
     */
    public void stopCamp(CourseRound courseRound) {
        // 1.change courseStatus to finish and change finished to true
        genericDaoSupport.update(courseRound.finishCourseRound());
        
        // change schedule and ready course_schedule to disabled
        List<CourseSchedule> courseSchedules = courseRound.getSchedules();
        for(CourseSchedule courseSchedule : courseSchedules) {
            if(courseSchedule.getScheduleStatus().isSchedule() || courseSchedule.getScheduleStatus().isReady()) {
                genericDaoSupport.update(courseSchedule.disabled());
            }
        }
        
        //记录初始与结营时三围和inbody
        courseDaoSupport.recordCourseRoundResult(courseRound);
        
        //2. change status of order
        Order order = courseRound.getOrder();
        order.setOrderStatus(OrderStatus.COMPLETE);
        genericDaoSupport.update(order);
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#delay(co.fitstart.entity.course.round.CourseRound, java.util.Date, co.fitstart.admin.entity.security.Login)
     */
    public void delay(CourseRound courseRound, Date effectiveEnd, Login operator) {

        logger.info("delay isIndividual[{}] isExperience [{}] isYear [{}]",courseRound.getCourseType().isIndividual(),courseRound.getCourse().isExperience(),courseRound.getCourseType().isYear());

        if(((courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isSpecial() || courseRound.getCourseType().isPosture()) && !courseRound.getCourse().isExperience()) || courseRound.getCourseType().isYear()) {
            logger.info("delay isReady [{}] isExpire [{}]",courseRound.getCourseStatus().isReady(),courseRound.getCourseStatus().isExpire());
            if(courseRound.getCourseStatus().isReady() || courseRound.getCourseStatus().isExpire() || courseRound.getCourseStatus().isForeverexpire() || courseRound.getCourseStatus().isFinish()) {
                if(!operator.isAdmin() && !operator.isRoot() && courseRound.getEffectiveEnd() != null && courseRound.getEffectiveEnd().after(effectiveEnd)) {
                    throw new BusinessException("延期失败，所选时间小于课程原有效期");
                }
                
                if(courseRound.getFitnessGroup() == null) {
                    courseRound.addAttribute(Constant.DELAY_OPERATOR, operator.getId());
                    courseRound.addAttribute(Constant.DELAY_OPERATE_TIME, DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
                    genericDaoSupport.update(courseRound.delay(effectiveEnd));
                } else {
                    Integer originalWeek = null;
                    if(MapUtils.isNotEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) != null) {
                        originalWeek = Integer.parseInt(courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT).getValue());
                    }
                    
                    courseRound.addAttribute(Constant.DELAY_OPERATOR, operator);
                    courseRound.addAttribute(Constant.DELAY_OPERATE_TIME, DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
                    genericDaoSupport.update(courseRound.delay(effectiveEnd));
                    
                    if(originalWeek == null) {
                        this.initBatchCourseware(courseRound);
                    } else {
                        this.updateBatchCourseware(courseRound, originalWeek);
                    }
                }
                
                if(courseRound.getCourseStatus().isActivate() || courseRound.getCourseStatus().isFinish()) {
                    //send message to site manager
                	Principal manager = courseRound.getSite().getManager();
                	if(manager != null && !manager.isDisabled() ) {
                		String mobile = courseRound.getSite().getManager().getMobile();
                        
                        String student = courseRound.getStudent().getName();
                        String courseName = courseRound.getCourse().getName();
                        
                        smsSender.sendCourseRoundCoachNotice(mobile, student, courseName);
                	}
                } else if(courseRound.getCourseStatus().isReady()) {
                    //send message to student
                    String mobile = courseRound.getStudent().getMobile();
                    String site = courseRound.getSite().getName();
                    
                    if(courseRound.getCourseType().isYear()) {
                        smsSender.sendYearDelayNotice(mobile, site, DateFormatUtils.format(effectiveEnd, "yyyy/MM/dd"));
                    } else if((courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isSpecial() || courseRound.getCourseType().isPosture()) && !courseRound.getCourse().isExperience()) {
                        int courseAmount = courseRound.getCourseAmount();
                        
                        smsSender.sendIndividualDelayNotice(mobile, site, courseAmount, DateFormatUtils.format(effectiveEnd, "yyyy/MM/dd"));
                    }
                }
            }
        }
    }
    
    @Override
    public void delayAllBYCovid() throws ParseException {
    	// TODO Auto-generated method stub
    	
//    	Map<String, Object> params = new HashMap<String, Object>();
//    	String sql1 = "FROM CourseRound WHERE site.id not in (11,14) AND courseStatus = :status AND effectiveEnd IS NOT NULL";
//    	params.put("status", RoundStatus.READY);
//    	List<CourseRound> courseRounds = genericDaoSupport.searchForList(sql1, params);
//    	logger.info("get ready course round size [{}]", courseRounds.size());
//    	
//    	for(CourseRound courseRound : courseRounds) {
//    		Date endTime = DateUtils.addDays(courseRound.getEffectiveEnd(), 62);
//    		courseRound.setEffectiveEnd(endTime);
//    		genericDaoSupport.update(courseRound);
//    	}
    	
    	Map<String, Object> params2 = new HashMap<String, Object>();
    	String sql2 = "FROM CourseRound WHERE site.id not in (11,14) AND courseStatus = :status AND effectiveEnd >= :begin AND effectiveEnd <= :end";
    	params2.put("status", RoundStatus.EXPIRE);
    	Date beginDate = org.apache.commons.lang3.time.DateUtils.parseDate("2022-04-01", "yyyy-MM-dd");
    	params2.put("begin", beginDate);
    	params2.put("end", org.apache.commons.lang3.time.DateUtils.parseDate("2022-06-02", "yyyy-MM-dd"));
    	@SuppressWarnings("unchecked")
		List<CourseRound> courseRounds2 = genericDaoSupport.searchForList(sql2, params2);
    	logger.info("get expire course round size [{}]", courseRounds2.size());
    	
    	for(CourseRound courseRound : courseRounds2) {
			int between = DateUtils.daysBetween(courseRound.getEffectiveEnd(), beginDate);
			Date endTime = DateUtils.addDays(courseRound.getEffectiveEnd(), between);
			courseRound.setEffectiveEnd(endTime);
			genericDaoSupport.update(courseRound);
		}
    	
    	
    	Map<String, Object> params3 = new HashMap<String, Object>();
    	String sql3 = "FROM CourseRound WHERE site.id not in (11,14) AND courseType = :courseType  AND courseStatus = :status AND effectiveEnd >= :begin AND effectiveEnd <= :end";
    	params3.put("status", RoundStatus.FINISH);
    	params3.put("begin", org.apache.commons.lang3.time.DateUtils.parseDate("2022-04-01", "yyyy-MM-dd"));
    	params3.put("end", org.apache.commons.lang3.time.DateUtils.parseDate("2022-06-02", "yyyy-MM-dd"));
    	params3.put("courseType", CourseType.YEAR);
    	@SuppressWarnings("unchecked")
		List<CourseRound> courseRounds3 = genericDaoSupport.searchForList(sql3, params3);
    	logger.info("get expire course round size [{}]", courseRounds3.size());
    	
    	for(CourseRound courseRound : courseRounds3) {
			int between = DateUtils.daysBetween(courseRound.getEffectiveEnd(), beginDate);
			Date endTime = DateUtils.addDays(courseRound.getEffectiveEnd(), between);
			courseRound.setEffectiveEnd(endTime);
			genericDaoSupport.update(courseRound);
		}
    	
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#activate(co.fitstart.entity.course.round.CourseRound)
     */
    public void activate(CourseRound courseRound) {
        genericDaoSupport.update(courseRound.activate());
        
        String mobile = courseRound.getStudent().getMobile();
        String site = courseRound.getSite().getName();
        int courseAmount = courseRound.getCourseAmount();
        
        if(courseRound.getCourseStatus().isReady()) {
            if(courseRound.getEffectiveEnd() != null) {
                String effectiveEnd = DateFormatUtils.format(courseRound.getEffectiveEnd(), "yyyy/MM/dd");
                
                if(courseRound.getCourseType().isYear()) {
                    smsSender.sendYearDelayNotice(mobile, site, effectiveEnd);
                } else if((courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isPosture() || courseRound.getCourseType().isPosture()) && !courseRound.getCourse().isExperience()) {
                    smsSender.sendIndividualDelayNotice(mobile, site, courseAmount, effectiveEnd);
                }
        	}
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#changeSite(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal)
     */
    public void changeSite(CourseRound courseRound, Site site, Principal coach) {
        
        PrincipalReportRelation relation = principalDao.getRelation(coach, site);
        if(relation == null) {
            throw new BusinessException("所选门店与教练不一致");
        }
        
        // 1.change courseRound's site and coach
        genericDaoSupport.update(courseRound.changeSite(site, coach));
        
        // 2.change courseSchedule
        List<CourseSchedule> schedules = courseRound.getSchedules();
        
        // For individual courseRound whose coach has not been arranged, create its schedules
        if( schedules.isEmpty() && (courseRound.getCourseType().equals(CourseType.INDIVIDUAL) || courseRound.getCourseType().equals(CourseType.SPECIAL) || courseRound.getCourseType().equals(CourseType.POSTURE))) {
            
            // Calculate the number of schedules by retrieving Course and courseRoundAttr
            Course course = courseRound.getCourse();
            int hours = course.getHours();
            if(!courseRound.getAttrs().isEmpty() && courseRound.getAttrs().get(Constant.COURSE_AMOUNT) != null) {
                hours = Integer.parseInt(courseRound.getAttrs().get(Constant.COURSE_AMOUNT).getValue());
            }
            
            for(int i = 0; i < hours; i++ ) {
                CourseSchedule courseSchedule = new CourseSchedule(courseRound, ScheduleStatus.SCHEDULE);
                genericDaoSupport.save(courseSchedule);
            }
        }
        
        if(courseRound.getCourseType().isYear()) {
            for(CourseSchedule schedule : schedules) {
                if (schedule.getScheduleStatus().isReady()) {
                    genericDaoSupport.update(schedule.disabled());
                }
            }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#batchChangeGroup(java.lang.String, java.lang.String, co.fitstart.entity.courseware.fitnessgroup.FitnessGroup, java.lang.String)
     */
    public void batchChangeGroup(String roundCode, String groupName, FitnessGroup fitnessGroup, String tagIds) {
        List<CourseRound> courseRounds = new ArrayList<CourseRound>();
        if(courseRoundDao.listCourseRoundGroup(roundCode).isEmpty()) {
            courseRounds = courseRoundDao.listRoundByCode(roundCode, RoundStatus.PREPARE);
        } else if(StringUtils.isEmpty(groupName)) {
            courseRounds = courseRoundDao.listUnfinishRoundByCode(new OnlineRoundGroupStatus[]{OnlineRoundGroupStatus.DISALLOW_GROUP, OnlineRoundGroupStatus.UNGROUPED}, roundCode, RoundStatus.PREPARE);
        } else {
            courseRounds = courseRoundDao.listfinishRoundByGroup(roundCode, groupName);
        }
        
        for(CourseRound courseRound : courseRounds) {
            if(courseRound.getFitnessGroup() == null) {
                this.changeGroup(courseRound, fitnessGroup, tagIds);
            }
        }
        
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#changeGroup(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.courseware.fitnessgroup.FitnessGroup, java.lang.String)
     */
    public void changeGroup(CourseRound courseRound, FitnessGroup fitnessGroup, String tagIds) {
        // change courseRound's fitnessGroup
        courseRound.addAttribute("courseware_weekly_amount", fitnessGroup.getWeeks());
        if(!courseRound.getCourseType().isOnline()) {
            courseRound.addAttribute("courseware_course_amount", fitnessGroup.getHours());
        }
        genericDaoSupport.update(courseRound.changeGroup(fitnessGroup));
        
        // create batchCourseware, dailyCourseware, courseware
        this.createBatchCourseware(fitnessGroup, courseRound);
        
        // change student's fitnessTag
        if(StringUtils.isNotEmpty(tagIds)) {
            this.createTags(courseRound.getStudent(), tagIds);
        }
        
    }
    
    /**
     * 
     * @param student
     * @param tagIds
     */
    private void createTags(Student student, String tagIds) {
        String tags = "";
        List<FitnessTag> fitnessTags = fitnessTagDao.list(this.getArrayTagIds(tagIds));
        for(FitnessTag fitnessTag : fitnessTags) {
            CoursewareTag coursewareTag = new CoursewareTag(fitnessTag);
            Long key = (Long) genericDaoSupport.save(coursewareTag);
            tags = tags + "," + key.toString();
        }
        
        genericDaoSupport.update(student.editTag(tags));
    }
    
    /**
     * 
     * @param tagIds
     * @return
     */
    private List<Long> getArrayTagIds(String tagIds) {
        List<Long> arrayTagIds = new ArrayList<>();
        if(tagIds != null) {
            for(String tagId : StringUtils.split(tagIds, ",")) {
                arrayTagIds.add(Long.parseLong(tagId));
            }
        }
        return arrayTagIds;
    }
    
    /**
     * 
     * @param fitnessGroup
     * @param courseRound
     * @return
     */
    private void createBatchCourseware(FitnessGroup fitnessGroup, CourseRound courseRound) {
        List<BatchCoursewareTemplate> batchCoursewareTemplates = groupTemplateDao.listBatchCoursewareTemplate(fitnessGroup);
        
        for(BatchCoursewareTemplate batchCoursewareTemplate : batchCoursewareTemplates) {
            BatchCourseware batchCourseware = new BatchCourseware(batchCoursewareTemplate, courseRound);
            genericDaoSupport.save(batchCourseware);
            
            // create dietaryProgram
            this.createDietaryPrograms(batchCoursewareTemplate, batchCourseware);
            
            // create dailyCourseware
            this.createDailyCourseware(batchCoursewareTemplate, batchCourseware);
            
        }
    }
    
    /**
     * 
     * @param batchCoursewareTemplate
     * @param batchCourseware
     */
    private void createDailyCourseware(BatchCoursewareTemplate batchCoursewareTemplate, BatchCourseware batchCourseware) {
        List<DailyCoursewareTemplate> dailyCoursewareTemplates = groupTemplateDao.listDailyCoursewareTemplate(batchCoursewareTemplate);
        
        for(DailyCoursewareTemplate template : dailyCoursewareTemplates) {
            DailyCourseware dailyCourseware = new DailyCourseware(template, batchCourseware);
            genericDaoSupport.save(dailyCourseware);
            
            // create courseware
            this.createCourseware(template, dailyCourseware);
        }
    }
    
    /**
     * 
     * @param batchCoursewareTemplate
     * @param batchCourseware
     */
    private void createDietaryPrograms(BatchCoursewareTemplate batchCoursewareTemplate, BatchCourseware batchCourseware) {
        if(batchCoursewareTemplate.getBreakfast() != null) {
            this.createDietaryProgram(batchCourseware, batchCoursewareTemplate.getBreakfast());
        }
        
        if(batchCoursewareTemplate.getLunch() != null) {
            this.createDietaryProgram(batchCourseware, batchCoursewareTemplate.getLunch());
        }
        
        if(batchCoursewareTemplate.getDinner() != null) {
            this.createDietaryProgram(batchCourseware, batchCoursewareTemplate.getDinner());
        }
        
    }
    
    /**
     * 
     * @param batchCourseware
     * @param template
     */
    private void createDietaryProgram(BatchCourseware batchCourseware, DietaryProgramTemplate template) {
        DietaryProgram snacks = null;
        if(template.getSnacks() != null) {
            DietaryProgramTemplate templateSnacks = genericDaoSupport.load(DietaryProgramTemplate.class, template.getSnacks());
            snacks = new DietaryProgram(templateSnacks, batchCourseware);
            genericDaoSupport.save(snacks);
        }
        
        DietaryProgram dietaryProgram = new DietaryProgram(template, batchCourseware, snacks == null ? null : snacks.getId());
        genericDaoSupport.save(dietaryProgram);
    }
    
    /**
     * 
     * @param dailyCoursewareTemplate
     * @param dailyCourseware
     */
    private void createCourseware(DailyCoursewareTemplate dailyCoursewareTemplate, DailyCourseware dailyCourseware) {
        List<CoursewareTemplate> templates = groupTemplateDao.listCoursewareTemplate(dailyCoursewareTemplate);
        for(CoursewareTemplate template : templates) {
            Courseware courseware = new Courseware(template, dailyCourseware);
            genericDaoSupport.save(courseware);
        }
        
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#editGroup(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.courseware.fitnessgroup.FitnessGroup)
     */
    public void editGroup(CourseRound courseRound, FitnessGroup fitnessGroup) {
        // change courseRound's fitnessGroup
        genericDaoSupport.update(courseRound.changeGroup(fitnessGroup));
        
        this.updataBatchCourseware(courseRound, fitnessGroup);
    }
    
    /**
     * 
     * @param courseRound
     * @param fitnessGroup
     */
    private void updataBatchCourseware(CourseRound courseRound, FitnessGroup fitnessGroup) {
        // get unFinished batch
        List<BatchCourseware> batchCoursewares = groupDao.listBatchCourseware(courseRound);
        
        // get batchCoursewareTemplate
        List<BatchCoursewareTemplate> batchCoursewareTemplates = groupTemplateDao.listBatchCoursewareTemplate(fitnessGroup);
        
        for(BatchCourseware batchCourseware : batchCoursewares) {
            for(BatchCoursewareTemplate batchCoursewareTemplate : batchCoursewareTemplates) {
                if(batchCourseware.getType().equals(batchCoursewareTemplate.getType()) && batchCourseware.getSorting() == batchCoursewareTemplate.getSorting()) {
                    genericDaoSupport.update(batchCourseware.edit(batchCoursewareTemplate));
                    
                    // update dietaryPrograms
                    this.updateDietaryPrograms(batchCourseware, batchCoursewareTemplate);
                    
                    // update dailyCourseware
                    this.updateDailyCourseware(batchCourseware, batchCoursewareTemplate);
                    
                }
            }
            
        }
    }
    
    /**
     * 
     * @param batchCourseware
     * @param batchCoursewareTemplate
     */
    @SuppressWarnings("unlikely-arg-type")
	private void updateDietaryPrograms(BatchCourseware batchCourseware, BatchCoursewareTemplate batchCoursewareTemplate) {
        if(batchCoursewareTemplate.getBreakfast() != null) {
            this.updateDietaryProgram(batchCourseware.getDietaryPrograms().get(DietaryProgramType.BREAKFAST), batchCoursewareTemplate.getBreakfast(), batchCourseware);
        }
        
        if(batchCoursewareTemplate.getLunch() != null) {
            this.updateDietaryProgram(batchCourseware.getDietaryPrograms().get(DietaryProgramType.LUNCH), batchCoursewareTemplate.getLunch(), batchCourseware);
        }
        
        if(batchCoursewareTemplate.getDinner() != null) {
            this.updateDietaryProgram(batchCourseware.getDietaryPrograms().get(DietaryProgramType.DINNER), batchCoursewareTemplate.getDinner(), batchCourseware);
        }
    }
    
    /**
     * 
     * @param dietaryProgram
     * @param template
     */
    private void updateDietaryProgram(DietaryProgram dietaryProgram, DietaryProgramTemplate template, BatchCourseware batchCourseware) {
        if(dietaryProgram == null) {
            DietaryProgram snacks = null;
            if(template.getSnacks() != null) {
                DietaryProgramTemplate templateSnacks = genericDaoSupport.load(DietaryProgramTemplate.class, template.getSnacks());
                snacks = new DietaryProgram(templateSnacks, batchCourseware);
                genericDaoSupport.save(snacks);
            }
            
            DietaryProgram newDiet = new DietaryProgram(template, batchCourseware, snacks == null ? null : snacks.getId());
            genericDaoSupport.save(newDiet);
        } else {
            
            // update dietaryProgram's snacks first
            if(template.getSnacks() != null) { // if template has snacks edit dietaryProgram's snacks
                DietaryProgramTemplate snacksTemplate = genericDaoSupport.load(DietaryProgramTemplate.class, template.getSnacks());
                if(dietaryProgram.getSnacks() == null) {
                    DietaryProgram snacks = new DietaryProgram(template, dietaryProgram.getBatch());
                    genericDaoSupport.save(snacks);
                    
                    dietaryProgram.editSnacks(snacks.getId());
                } else {
                    DietaryProgram snacks = genericDaoSupport.load(dietaryProgram.getClass(), dietaryProgram.getSnacks());
                    genericDaoSupport.update(snacks.edit(snacksTemplate));
                }
            } else { // if template doesn't has snacks set dietaryProgram's snacks to null
                if(dietaryProgram.getSnacks() != null) {
                    DietaryProgram snacks = genericDaoSupport.load(dietaryProgram.getClass(), dietaryProgram.getSnacks());
                    genericDaoSupport.update(snacks.disable());
                    
                    dietaryProgram.editSnacks(null);
                }
            }
            
            genericDaoSupport.update(dietaryProgram.edit(template));
        }
    }
    
    /**
     * 
     * @param batchCourseware
     * @param batchCoursewareTemplate
     */
    private void updateDailyCourseware(BatchCourseware batchCourseware, BatchCoursewareTemplate batchCoursewareTemplate) {
        List<DailyCourseware> dailyCoursewares = groupDao.listUnfinishDailyCourseware(batchCourseware);
        List<DailyCoursewareTemplate> dailyCoursewareTemplates = groupTemplateDao.listDailyCoursewareTemplate(batchCoursewareTemplate);
        for (DailyCourseware dailyCourseware : dailyCoursewares) {
            for(DailyCoursewareTemplate dailyCoursewareTemplate : dailyCoursewareTemplates) {
                if(dailyCourseware.getSorting() == dailyCoursewareTemplate.getSorting()) {
                    genericDaoSupport.update(dailyCourseware.edit(dailyCoursewareTemplate));
                    
                    // delete original courseware first
                    groupDao.deleteCourseware(dailyCourseware);
                    this.createCourseware(dailyCoursewareTemplate, dailyCourseware);
                }
            }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#editTag(co.fitstart.entity.course.round.CourseRound, java.lang.String)
     */
    public void editTag(CourseRound courseRound, String tagIds) {
        List<Long> tags = this.getArrayTagIds(courseRound.getStudent().getTagIds());
        if(!tags.isEmpty()) {
            fitnessTagDao.deleteCoursewareTags(tags);
        }
        this.createTags(courseRound.getStudent(), tagIds);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#loadCourseRound(co.fitstart.entity.order.Order)
     */
    public CourseRound loadCourseRound(Order order) {
        return courseRoundDao.loadCourseRound(order);
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#addExperienceCourse(co.fitstart.entity.order.Order, co.fitstart.entity.principal.Principal, co.fitstart.entity.site.Site, co.fitstart.admin.entity.course.ExperienceStat)
     */
    public boolean addExperienceCourse(Order order, Principal coach, Site site, ExperienceStat experienceStat) {
        // Step 2. call api to make a payment
        Result result = paymentHandler.payOrder(order.getCode(), order.getUser().getId(), order.getPrice(), "ADMIN_CREATED_EXPERIENCE_ORDER", PaymentMethod.OTHER.toString());
        
        if(result.isValid()) {
            // Step 3. create coach rest calendar
            Date dayhour = DateUtils.parseDate(DateFormatUtils.format(experienceStat.getDate(), "yyyy-MM-dd ") + experienceStat.getExperienceHour(), "yyyy-MM-dd HH:mm");
            String restHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(dayhour, 1), "HH:mm");
            CoachRestCalendar calendar = new CoachRestCalendar(coach, CoachRestType.EXPERIENCE, experienceStat.getDate(), restHour, true);
            genericDaoSupport.save(calendar);
            
            // Step 4. activate this course round, update round status
            CourseRound courseRound = this.loadCourseRound(order);
            
            this.activate(courseRound);
            
            // Step 5. bind main coach to this course round and create course schedules 
            this.bindIndividualCourseCoach(courseRound, coach);
            
            // Step 6. arrange schedule time
            this.arrangeExperience(courseRound.getId(), experienceStat.getDate(), experienceStat.getExperienceHour(), coach, site, experienceStat.getRoomType());
            
            return true;
        } else {
            genericDaoSupport.update(order.disable());
            
            logger.error("addExperienceCourse error - ", result.getMessage());
            return false;
        }
    }
    
     /**
      * 
      * @param courseRound
      * @param coach
      */
     private void bindIndividualCourseCoach(CourseRound courseRound, Principal coach) {
            
            if(!courseRound.getSchedules().isEmpty()) {
                throw new BusinessException("您已绑定教练,请不要重复绑定");
            }
            
            // Step1 bind course round coach
            genericDaoSupport.update(courseRound.bindCoach(coach));
            
            // Step2 create course schedule by course & courseRound
            Course course = courseRound.getCourse();
            
            int hours = course.getHours();
            
            // if hours changed
            if(!courseRound.getAttrs().isEmpty() && courseRound.getAttrs().get(Constant.COURSE_AMOUNT) != null) {
                hours = Integer.parseInt(courseRound.getAttrs().get(Constant.COURSE_AMOUNT).getValue());
            }
            
            for(int i = 0; i < hours; i++ ) {
                CourseSchedule courseSchedule = new CourseSchedule(courseRound, ScheduleStatus.SCHEDULE);
                genericDaoSupport.save(courseSchedule);
            }
            logger.trace("bindIndividualCourseCoach - courseRound [{}] save [{}] courseSchedules", courseRound.getId(), hours);
            
            // Step3 create new principal message
            PrincipalMessage principalMessage = new PrincipalMessage(coach);
            genericDaoSupport.save(principalMessage.initialize("新学员", "您有新的学员", "您有新的学员【" + courseRound.getStudent().getName() + "】，请在我的学员点击查看，并排课"));
            
            // Step4 send tencent message to coach
            tencentHandler.sendMessage("您有新的学员【" + courseRound.getStudent().getName() + "】，请在我的学员点击查看，并排课", coach.getMobile());
        }
     
     /**
      * 
      * @param courseRoundId
      * @param experienceDate
      * @param experienceHour
      * @param coach
      */
        public void arrangeExperience(Long courseRoundId, Date experienceDate, String experienceHour, Principal coach, Site site, String roomType) {
            String sentence = "FROM CourseSchedule WHERE disabled = false AND courseRound.id = :courseRoundId";
            CourseSchedule courseSchedule = genericDaoSupport.searchForObject(sentence, "courseRoundId", courseRoundId, CourseSchedule.class);
            
            String day = DateFormatUtils.format(experienceDate, "yyyy-MM-dd");
            
            courseSchedule.schedule(day, experienceHour).changeCoach(coach);
            
            // save roomScheduleRecord 
            RoomScheduleRecord roomScheduleRecord = new RoomScheduleRecord(site, courseSchedule.getCourseRound().getCode(), courseSchedule.getOccurDate(), courseSchedule.getOccurHour(), RoomType.aliasOf(roomType), 1);
            genericDaoSupport.save(roomScheduleRecord);
            
            genericDaoSupport.update(courseSchedule.roomScheduleRecord(roomScheduleRecord));
     }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#cancelExperienceCourse(co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public void cancelExperienceCourse(CourseSchedule courseSchedule) {
        // 1. update courseRound's status
        genericDaoSupport.update(courseSchedule.getCourseRound().changeStatus(RoundStatus.CANCEL));
        
        // 2 disable schedule
        genericDaoSupport.update(courseSchedule.disabled());
        
        // 3 disable roomSchedule
        if(courseSchedule.getRoomSchedule() != null) {
            genericDaoSupport.update(courseSchedule.getRoomSchedule().disabled());
        }
        
        // 4 disable rest calendar
        Date dayhour = DateUtils.parseDate(DateFormatUtils.format(courseSchedule.getOccurDate(), "yyyy-MM-dd") + " " + courseSchedule.getOccurHour(), "yyyy-MM-dd HH:mm");
        String restHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(dayhour, 1), "HH:mm");
        CoachRestCalendar coachRestCalendar = coachRestCalendarDao.loadCoachRestCalendar(courseSchedule.getCoach(), courseSchedule.getOccurDate(), restHour, CoachRestType.EXPERIENCE);
        if(coachRestCalendar != null) {
            genericDaoSupport.update(coachRestCalendar.disable());
        }
        
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#honesty(co.fitstart.entity.course.round.CourseRound)
     */
    public void honesty(CourseRound courseRound) {
        if(courseRound.isDishonesty()) {
            // 1. update attr's value
            genericDaoSupport.update(courseRound.editAttribute("dishonesty", "0"));
            
            // 2. disable expiry courseSchedule
            courseScheduleDao.disableSchedule(courseRound, ScheduleStatus.EXPIRY);
            
            scheduleDaoSupport.recalculateStudentCourseware(courseRound);
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#search(co.fitstart.admin.entity.search.CourseRoundSearch, com.demo2do.core.support.Page)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRound> search(CourseRoundSearch courseRoundSearch, Page page) {
        String s = courseRoundSearch.buildSentenceTemplate();
        Map<String, Object> parameters = courseRoundSearch.buildParameters();
        List<CourseRound> result = genericDaoSupport.searchForList(s, parameters, page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#totalCourseRound(co.fitstart.admin.entity.search.CourseRoundSearch)
     */
    public int totalCourseRound(CourseRoundSearch courseRoundSearch) {
        return genericDaoSupport.count(courseRoundSearch.buildSentenceTemplate(), courseRoundSearch.buildParameters());
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#validateCoachSite(co.fitstart.entity.principal.Principal, co.fitstart.entity.site.Site, java.util.Date)
     */
    public void validateCoachSite(Principal coach, Site site, Date day) {
        List<Site> sites = coachDao.listCoachSite(coach, "%" + getDayOfWeek(day) + ",%");
        if(!sites.contains(site)) {
            throw new BusinessException("教练所选日期在所选门店不可预约");
        }
    }
    
    /**
     * 
     * @param courseRound
     * @param day
     * @param hour
     */
    private void validateEffectiveEnd(CourseRound courseRound, Date day, String hour) {
        if(courseRound.getEffectiveEnd() != null && DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + hour, "yyyy-MM-dd HH:mm").after(courseRound.getEffectiveEnd())) {
            throw new BusinessException("所选时间该课程已过期");
        }
    }
    
    /**
     * 
     * @param day
     * @return
     */
    private int getDayOfWeek(Date day) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(day);
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        
        if(dayOfWeek == 1) {
            return 7;
        } else {
            return dayOfWeek -1;
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listCourseRoundFeedback(co.fitstart.entity.site.Site, java.lang.Boolean, com.demo2do.core.support.Page)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRoundFeedback> listCourseRoundFeedback(Site site, Boolean handled, Page page) {
        CourseRoundFeedbackSearch search = new CourseRoundFeedbackSearch();
        
        search.setSite(site);
        search.setHandled(handled);
        
        String s = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        List<CourseRoundFeedback> result = genericDaoSupport.searchForList(s, parameters, page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listCourseRoundFeedback(java.util.List, java.lang.Boolean, com.demo2do.core.support.Page)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRoundFeedback> listCourseRoundFeedback(List<Site> sites, Boolean handled, Page page) {
        CourseRoundFeedbackSearch search = new CourseRoundFeedbackSearch();
        
        search.setSites(sites);
        search.setHandled(handled);
        
        String s = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        List<CourseRoundFeedback> result = genericDaoSupport.searchForList(s, parameters, page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#loadCourseRoundFeedback(java.lang.Long)
     */
    public CourseRoundFeedback loadCourseRoundFeedback(Long feedbackId) {
        return genericDaoSupport.load(CourseRoundFeedback.class, feedbackId);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#handle(co.fitstart.entity.course.round.CourseRoundFeedback, java.lang.String, co.fitstart.entity.principal.Principal)
     */
    public void handle(CourseRoundFeedback feedback, String remark, Principal handler) {
        genericDaoSupport.update(feedback.handle(remark, handler));
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#search(co.fitstart.admin.entity.search.CourseRoundFeedbackSearch, com.demo2do.core.support.Page)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRoundFeedback> search(CourseRoundFeedbackSearch search, Page page) {
        String s = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        List<CourseRoundFeedback> result = genericDaoSupport.searchForList(s, parameters, page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#search(co.fitstart.admin.entity.search.CourseRoundFeedbackSearch)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRoundFeedback> search(CourseRoundFeedbackSearch search) {
        String s = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        return genericDaoSupport.searchForList(s, parameters);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#loadCourseRoundResult(co.fitstart.entity.course.round.CourseRound)
     */
    public CourseRoundResult loadCourseRoundResult(CourseRound courseRound) {
        return courseRoundResultDao.load(courseRound);
    }
    
    @Override
    public List<CourseRound> listAll(Student student) {
    	return courseRoundDao.listAll(student);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#list
     * Main(co.fitstart.entity.user.Student)
     */
    public List<CourseRound> listMain(Student student) {
        return courseRoundDao.listBindableMain(CourseType.INDIVIDUAL, Arrays.asList(new RoundStatus[]{RoundStatus.PREPARE, RoundStatus.READY, RoundStatus.HOLD, RoundStatus.EXPIRE, RoundStatus.ACTIVATE, RoundStatus.FINISH}), student);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listMain(co.fitstart.entity.user.Student, co.fitstart.entity.site.Site)
     */
    public List<CourseRound> listMain(Student student, Site site) {
        return courseRoundDao.listBindableMain(CourseType.INDIVIDUAL, Arrays.asList(new RoundStatus[]{RoundStatus.PREPARE, RoundStatus.READY, RoundStatus.HOLD, RoundStatus.EXPIRE, RoundStatus.ACTIVATE, RoundStatus.FINISH}), student, site);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listMain(co.fitstart.entity.user.Student, java.util.List)
     */
    public List<CourseRound> listMain(Student student, List<Site> sites) {
        return courseRoundDao.listBindableMain(CourseType.INDIVIDUAL, Arrays.asList(new RoundStatus[]{RoundStatus.PREPARE, RoundStatus.READY, RoundStatus.HOLD, RoundStatus.EXPIRE, RoundStatus.ACTIVATE, RoundStatus.FINISH}), student, sites);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#bindMain(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.course.round.CourseRound)
     */
    public void bindMain(CourseRound give, CourseRound main) {
        if(give.getStudent() != main.getStudent()) {
            throw new BusinessException("不是同一学员的课程，不可绑定");
        } else if(!give.isGive()) {
            throw new BusinessException("非赠课不可绑定主课程");
        } else if(give.getMainCourseRound() != null) {
            throw new BusinessException("此赠课已绑定主课程，不可重复绑定");
        } else if(main.getGiveCourseRound() != null) {
            throw new BusinessException("此主课程已有绑定的赠课，不可重复绑定");
        } else if(!main.getCourseType().isIndividual() || main.getCourseStatus().isCancel() || main.getCourse().isExperience() || main.isGive()) {
            throw new BusinessException("主课程状态有误，不可绑定");
        } else{
            genericDaoSupport.update(give.bindMain(main));
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#unbindMain(co.fitstart.entity.course.round.CourseRound)
     */
    public void unbindMain(CourseRound give) {
        genericDaoSupport.update(give.unbindMain());
    }

    /**
     * 
     * @param courseRound
     */
    private void updateBatchCourseware(CourseRound courseRound, int originalWeek) {
        if(courseRound.getFitnessGroup() != null) {
            int week = courseRound.getEffectiveWeek();
            int plannedWeekly = groupDao.countBatchCourseware(courseRound, BatchCoursewareType.WEEKLY, week, CoursewareEditStatus.GENERATED);
            
            if(plannedWeekly < week) {
                courseRound.editingCourseware();
            } else {
                courseRound.editPlannedWeekly(plannedWeekly).finishCourseware();
            }
            genericDaoSupport.update(courseRound);
            
            Date effectiveEndDay = courseRound.getEffectiveEndDay();
            if(originalWeek > week) {
                BatchCourseware batch = groupDao.loadBatchCourseware(courseRound, BatchCoursewareType.WEEKLY, week);
                if(batch.getEnd().after(effectiveEndDay)) {
                    genericDaoSupport.update(batch.editEnd(effectiveEndDay));
                }
                
                this.disableBatchCourseware(courseRound, week);
            } else if(originalWeek == week) {
                BatchCourseware batch = groupDao.loadBatchCourseware(courseRound, BatchCoursewareType.WEEKLY, week);
                Date end = DateUtils.addDays(batch.getBegin(), 6);
                if(end.after(effectiveEndDay)) {
                    end = effectiveEndDay;
                }
                genericDaoSupport.update(batch.editEnd(end));
            } else {
                BatchCourseware batch = groupDao.loadBatchCourseware(courseRound, BatchCoursewareType.WEEKLY, originalWeek);
                Date batchEnd = DateUtils.addDays(batch.getBegin(), 6);
                genericDaoSupport.update(batch.editEnd(batchEnd));
                
                Date effectiveStart = null;
                if(courseRound.getEffectiveStart() != null) {
                    effectiveStart = com.demo2do.core.utils.DateUtils.parseDate(DateFormatUtils.format(courseRound.getEffectiveStart(), "yyyy-MM-dd"), "yyyy-MM-dd");
                }
                Map<Integer, BatchCoursewareTemplate> templateMap = this.mapBatchCoursewareTemplate(courseRound.getFitnessGroup());
                
                for(int i=originalWeek; i<week; i++) {
                    Date begin = null;
                    Date end = null;
                    if(effectiveStart != null) {
                        begin = DateUtils.addWeeks(effectiveStart, i);
                        end = DateUtils.addDays(begin, 6);
                    }
                    if(end != null && effectiveEndDay != null && end.after(effectiveEndDay)) {
                        end = effectiveEndDay;
                    }
                    BatchCourseware batchCourseware = new BatchCourseware(courseRound, i + 1, begin, end);
                    genericDaoSupport.save(batchCourseware);
                    
                    // create dietary
                    this.createDietary(templateMap.get(i%6), batchCourseware);
                }
            }
        }
    }

    /**
     * 
     * @param courseRound
     */
    private void initBatchCourseware(CourseRound courseRound) {
        if(courseRound.getFitnessGroup() != null) {
            int week = courseRound.getEffectiveWeek();
            int plannedWeekly = groupDao.countBatchCourseware(courseRound, BatchCoursewareType.WEEKLY, week, CoursewareEditStatus.GENERATED);
            
            if(plannedWeekly < week) {
                courseRound.editingCourseware();
            } else {
                courseRound.editPlannedWeekly(plannedWeekly).finishCourseware();
            }
            
            Date effectiveStart = null;
            if(courseRound.getEffectiveStart() != null) {
                effectiveStart = com.demo2do.core.utils.DateUtils.parseDate(DateFormatUtils.format(courseRound.getEffectiveStart(), "yyyy-MM-dd"), "yyyy-MM-dd");
            }
            Date effectiveEnd = courseRound.getEffectiveEndDay();
            
            List<BatchCourseware> batches = groupDao.listBatchCourseware(courseRound, BatchCoursewareType.WEEKLY);
            for(BatchCourseware batch : batches) {
                Date begin = null;
                Date end = null;
                if(effectiveStart != null) {
                    begin = DateUtils.addWeeks(effectiveStart, batch.getSorting() - 1);
                    end = DateUtils.addDays(begin, 6);
                }
                if(end != null && effectiveEnd != null && end.after(effectiveEnd)) {
                    end = effectiveEnd;
                }
                
                genericDaoSupport.update(batch.edit(begin, end));
            }
            
            if(week > courseRound.getFitnessGroupWeek()) {
                // get batchCoursewareTemplate
                Map<Integer, BatchCoursewareTemplate> templateMap = this.mapBatchCoursewareTemplate(courseRound.getFitnessGroup());
                
                for(int i=courseRound.getFitnessGroupWeek(); i<week; i++) {
                    BatchCoursewareTemplate template = templateMap.get(i%6);
                    
                    Date begin = null;
                    Date end = null;
                    if(effectiveStart != null) {
                        begin = DateUtils.addWeeks(effectiveStart, i);
                        end = DateUtils.addDays(begin, 6);
                    }
                    if(end != null && effectiveEnd != null && end.after(effectiveEnd)) {
                        end = effectiveEnd;
                    }
                    
                    BatchCourseware batchCourseware = new BatchCourseware(courseRound, i + 1, begin, end);
                    genericDaoSupport.save(batchCourseware);
                    
                    // create dietary
                    this.createDietary(template, batchCourseware);
                }
            } else if(week < courseRound.getFitnessGroupWeek()) {
                this.disableBatchCourseware(courseRound, week);
            }
        }
    }

    /**
     * 
     * @param template
     * @param batch
     */
    private void createDietary(BatchCoursewareTemplate template, BatchCourseware batch) {
        if(template != null) {
            DietaryProgramTemplate breakfast = template.getBreakfast();
            DietaryProgramTemplate breakfastSnack = null;
            DietaryProgramTemplate lunch = template.getLunch();
            DietaryProgramTemplate lunchSnack = null;
            DietaryProgramTemplate dinner = template.getDinner();
            DietaryProgramTemplate dinnerSnack = null;
            
            if(breakfast != null && breakfast.getSnacks() != null) {
                breakfastSnack = genericDaoSupport.load(DietaryProgramTemplate.class, breakfast.getSnacks());
            }
            
            if(lunch != null && lunch.getSnacks() != null) {
                lunchSnack = genericDaoSupport.load(DietaryProgramTemplate.class, lunch.getSnacks());
            }
            
            if(dinner != null && dinner.getSnacks() != null) {
                dinnerSnack = genericDaoSupport.load(DietaryProgramTemplate.class, dinner.getSnacks());
            }
            
            if(breakfast != null || breakfastSnack != null || lunch != null || lunchSnack != null || dinner != null || dinnerSnack != null) {
                Dietary dietary = new Dietary(batch);
                genericDaoSupport.save(dietary.edit(breakfast, breakfast == null ? null : breakfast.getDescription(),
                        breakfastSnack, breakfastSnack == null ? null : breakfastSnack.getDescription(),
                        lunch, lunch == null ? null : lunch.getDescription(),
                        lunchSnack, lunchSnack == null ? null : lunchSnack.getDescription(),
                        dinner, dinner == null ? null : dinner.getDescription(),
                        dinnerSnack, dinnerSnack == null ? null : dinnerSnack.getDescription(), null, null));
            }
        }
    }

    /**
     * 
     * @param fitnessGroup
     * @return
     */
    private Map<Integer, BatchCoursewareTemplate> mapBatchCoursewareTemplate(FitnessGroup fitnessGroup) {
        Map<Integer, BatchCoursewareTemplate> templateMap = new HashMap<>();
        List<BatchCoursewareTemplate> batchCoursewareTemplates = groupTemplateDao.listBatchCoursewareTemplate(fitnessGroup, BatchCoursewareType.WEEKLY, 6);
        
        for(BatchCoursewareTemplate template : batchCoursewareTemplates) {
            templateMap.put(template.getSorting() - 1, template);
        }
        
        return templateMap;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#rootInitCourseware(co.fitstart.entity.course.round.CourseRound)
     */
    public void rootInitCourseware(CourseRound courseRound) {
        if(!courseRound.isFinish() && !courseRound.getCourseStatus().isCancel() && courseRound.getFitnessGroup() != null) {
            int week;
            if(courseRound.getEffectiveStart() == null || courseRound.getEffectiveEnd() == null) {
                if((courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isSpecial() || courseRound.getCourseType().isPosture()) && !courseRound.getCourse().isExperience() && !MapUtils.isEmpty(courseRound.getAttrs())) {
                    CourseRoundAttribute courseRoundAttribute = courseRound.getAttrs().get(Constant.COURSE_AMOUNT);
                    
                    if(courseRoundAttribute != null && Integer.parseInt(courseRoundAttribute.getValue()) > courseRound.getCourse().getHours()) {
                        BigDecimal weeks = new BigDecimal(courseRoundAttribute.getValue()).subtract(new BigDecimal(courseRound.getCourse().getHours()))
                                                                                          .multiply(new BigDecimal(0.5))
                                                                                          .add(new BigDecimal(courseRound.getCourse().getExpires()));
                        int intWeeks = weeks.intValue();
                        if(new BigDecimal(intWeeks).compareTo(weeks) != 0) {
                            week = intWeeks + 1;
                        } else {
                            week = intWeeks;
                        }
                    }
                }
                
                week = courseRound.getCourse().getExpires();
            } else {
                Date effectiveStartDay = DateUtils.parseDate(DateFormatUtils.format(courseRound.getEffectiveStart(), "yyyy-MM-dd"), "yyyy-MM-dd");
                Date effectiveEndDay = courseRound.getEffectiveEndDay();
                
                int day = DateUtils.daysBetween(effectiveStartDay, effectiveEndDay) + 1;
                
                if(day%7 == 0 || day%7 == 1) {
                    week = day/7;
                } else {
                    week = day/7 + 1;
                }
            }
            
            
            if(MapUtils.isEmpty(courseRound.getAttrs()) || courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) == null) {
                genericDaoSupport.update(courseRound.addAttribute(Constant.ONLINE_PLAN_AMOUNT, week));
                
                this.initBatchCourseware(courseRound);
            } else {
                int originalWeek = Integer.parseInt(courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT).getValue());
                genericDaoSupport.update(courseRound.editAttribute(Constant.ONLINE_PLAN_AMOUNT, week));
                
                this.updateBatchCourseware(courseRound, originalWeek);
            }
        }
    }

    /**
     * 
     * @param courseRound
     * @param week
     */
    private void disableBatchCourseware(CourseRound courseRound, int week) {
        groupDao.disableBatchCourseware(courseRound, week);
        
        List<Dietary> dietarys = groupDao.listDieatry(courseRound, week);
        for(Dietary dietary : dietarys) {
            genericDaoSupport.update(dietary.disable());
        }
        
        List<Exercise> exercises = groupDao.listExercise(courseRound, week);
        for(Exercise exercise : exercises) {
            genericDaoSupport.update(exercise.disable());
        }
    }
    
    @Override
    public List<CourseRound> listOccuredExperience(String mobile) {
    	return courseRoundDao.listExperience(CourseType.INDIVIDUAL, new Long(4), Arrays.asList(new RoundStatus[]{RoundStatus.PREPARE, RoundStatus.READY, RoundStatus.HOLD, RoundStatus.EXPIRE, RoundStatus.ACTIVATE, RoundStatus.FINISH}), mobile);
    }
    
    @Override
    public List<CourseRound> listOccuredMain(String mobile) {
    	return courseRoundDao.listOrderMain(Arrays.asList(new CourseType[] {CourseType.INDIVIDUAL, CourseType.POSTURE, CourseType.SPECIAL, CourseType.YEAR}), Arrays.asList(new RoundStatus[]{RoundStatus.PREPARE, RoundStatus.READY, RoundStatus.HOLD, RoundStatus.EXPIRE, RoundStatus.ACTIVATE, RoundStatus.FINISH}), mobile);
    }
}
